# encoding = utf-8
import numpy as np
import pandas as pd
import statsmodels.api as sm

from application.logging import logger
from application.utils.CodeTimingUtil import CodeTimingUtil

"""
广义最小二乘法
GLS : Fit a linear model using Generalized Least Squares.

加权最小二乘法
WLS : Fit a linear model using Weighted Least Squares.

普通最小二乘法
OLS : Fit a linear model using Ordinary Least Squares.

参考资料:
https://baike.baidu.com/item/%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%E6%B3%95

线性回归(实战)
https://zhuanlan.zhihu.com/p/54129885

"""


# TODO function_cookie_student
@CodeTimingUtil(name="[学生化残差]function_cookie_student")
def function_cookie_student(x_train, y_train, x, y):
    """
    计算学生化残差:去除离群点
    :param x_train:
    :param y_train:
    :param x:
    :param y:
    :return:
    """
    #
    logger.info(f"计算学生化残差:开始-{'*' * 50}")

    # 数据转换:x_train
    x_train = np.array(x_train)
    logger.info(f"数据转换[x_train][np.array]:\n {x_train}")

    # 添加常量:x_train_ones,添加一列（第一列）数值都为1
    x_train_ones = sm.add_constant(x_train)
    logger.info(f"添加常量[x_train_ones][sm.add_constant]:\n {x_train_ones}")

    # 数据转换:y_train
    y_train = np.array(y_train)
    logger.info(f"数据转换[y_train][np.array]:\n {y_train}")

    # 创建模型:OLS:(Ordinary Least Squares, 普通最小二乘法)
    model_ols = sm.OLS(y_train, x_train_ones)

    # 训练模型
    logger.info(f"OLS模型训练开始[model_ols.fit()]: {model_ols}")
    model_ols_result = model_ols.fit()
    logger.info(f"OLS模型训练结束[model_ols.fit()]: {model_ols}")
    logger.info(f"OLS模型训练结果[model_ols_result]: {model_ols_result}")

    # 离群点检验: get_influence
    model_ols_result_get_influence = model_ols_result.get_influence()
    logger.info(f"OLS模型训练结果:[model_ols_result_get_influence]: {model_ols_result_get_influence}")

    # Cook距离: cooks_distance
    logger.info(f"OLS模型训练结果:[cooks_distance]:")
    cooks_distance = np.array(model_ols_result_get_influence.cooks_distance[0])
    logger.info(f"OLS模型训练结果:[cooks_distance]: \n {cooks_distance}")

    # TODO resid_studentized_external 非常缓慢,1093条数据用时3分06秒
    # resid_studentized_external
    logger.info(f"OLS模型训练结果:[resid_studentized_external-0]:")
    resid_studentized_external = model_ols_result_get_influence.resid_studentized_external
    logger.info(f"OLS模型训练结果:[resid_studentized_external-1]: \n {resid_studentized_external}")
    # resid_studentized_external = np.array(model_ols_result_get_influence.resid_studentized_external)
    # logger.info(f"OLS模型训练结果:[resid_studentized_external-2][np.array]: \n {resid_studentized_external}")

    # three_mean_cutoff
    logger.info(f"OLS模型训练结果:[three_mean_cutoff]:")
    three_mean_cutoff = 3 * np.mean(cooks_distance)
    logger.info(f"OLS模型训练结果:[three_mean_cutoff][3 * np.mean(cooks_distance)]: {three_mean_cutoff}")

    # list_three_mean_cutoff
    logger.info(f"OLS模型训练结果:[list_three_mean_cutoff]:")
    list_three_mean_cutoff = [three_mean_cutoff for i in range(len(cooks_distance))]
    # logger.info(f"OLS模型训练结果:[list_three_mean_cutoff]: \n {list_three_mean_cutoff}")
    logger.info(f"OLS模型训练结果:[list_three_mean_cutoff][len]: {len(list_three_mean_cutoff)}")

    # 组织数据
    df_data1 = pd.DataFrame(list_three_mean_cutoff, columns=["three_mean_cutoff"])
    df_data2 = pd.DataFrame(cooks_distance, columns=["cooks_distance"])
    df_data3 = pd.DataFrame(resid_studentized_external, columns=["学生化残差"])

    # 打印数据
    logger.info(f"df_data1[three_mean_cutoff]:\n {df_data1}")
    logger.info(f"df_data2[cooks_distance]:\n {df_data2}")
    logger.info(f"df_data3[学生化残差]:\n {df_data3}")

    #
    student_index = []
    cookie_index = []
    #
    student_index_data = []

    # 当学生化残差的绝对值大于2，则认为该样本点可能存在异常
    # for ->
    for i, _ in enumerate(resid_studentized_external.tolist()):
        # if ->
        if np.abs(_) > 2:  # 绝对值大于2
            student_index.append(i)  # 异常数据索引
            student_index_data.append(_)  # 异常数据数值
            pass  # <- if
        pass  # <- for
    #
    logger.info(f"学生化残差异常数据索引列表: \n {student_index}")
    logger.info(f"学生化残差异常数据内容列表: \n {student_index_data}")
    # 学生化残差异常值数量比例:当异常比例极低时（如5%以内），可以考虑直接删除
    student_outliers_ratio = len(student_index) / resid_studentized_external.shape[0]
    logger.info(f"学生化残差异常数据比例[{len(student_index)}/{resid_studentized_external.shape[0]}]: {student_outliers_ratio}")
    #
    data = pd.concat([x, y, df_data1, df_data2], axis=1)
    data_xue_sheng = pd.concat([x, y, df_data3], axis=1)

    #
    data["cookie-阈值"] = data["cooks_distance"] - data["three_mean_cutoff"]

    # for ->
    for j, _ in enumerate(list(data["cookie-阈值"])):
        # if ->
        if _ > 0:
            cookie_index.append(j)
            pass  # <- if
        pass  # <- for

    #
    logger.info(f"data['counter'][1]: \n {data}")
    data["counter"] = range(len(data))
    logger.info(f"data['counter'][2]: \n {data}")

    # 并集, 交集
    union = list(set(student_index).union(set(cookie_index)))
    intersection = list(set(student_index).intersection(set(cookie_index)))

    #
    logger.info(f"学生化残差并集:[union]:\n {union}")
    logger.info(f"学生化残差交集:[intersection]:\n {intersection}")

    #
    logger.info(f"计算学生化残差:结束-{'*' * 50}")

    # 返回数据
    return union, intersection
    pass


pass

if __name__ == '__main__':
    pass
